﻿using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

/* Manages Track and TrackGroup Objects via a simple interface so that the 
 * programmer does not need all of the ins and outs of the stuff to 
 * get things to work properly. Does most of the hard labor autonomously
 * 
 * Hopefully just a data set to do work so might not need to be bothered by Unity
 * Other revisions are necessary to make this work right.
 */
class TrackManager : MonoBehaviour
{
    /* Private variable declarations */
    private HashSet<TrackGroup> TG_List; // changed from List<T> to a HashSet<T>

    

    public TrackManager() 
    { 
        //Console.Write("TrackManager Initialized!\n");

        TG_List = new HashSet<TrackGroup>(); // changed to a HashSet from a List
        // add a TrackGroup to TG_List so that it does not have to be done later; good for initialization purposes
        TG_List.Add(new TrackGroup());

    } // for now will just do nothing but confirm initialization

    // *** REMOVED the destructor ~TrackManager() since it really has no purpose. Automatic GC will take care of it ***
	
	private TrackGroup searchForContainingTrackGroup(Track t_Track){
		foreach (TrackGroup TG in TG_List){
			if (TG.contains(t_Track)){
				return TG;
			}
		}
		return null;
	}
    private TrackGroup searchForContainingTrackGroup(int t_ID)
    {
        foreach (TrackGroup TG in TG_List){
            if (TG.getNumMembers() > 0){
                if (TG.contains(t_ID)){
                    Console.WriteLine("\nTrack[{0}] found in TrackGroup[{1}]\n", t_ID, TG.ID);
                    return TG;
                }
            }
        }
        // if a valid TG is not found, return null
        return null;
    }
    private void printSubTracks(HashSet<Track>[] subTracks)
    {
        for (int i = 0; i < subTracks.Length; i++)
        {
            Console.WriteLine("SubTrack[{0}] has {1} members", i, subTracks[i].Count());
            // print the members of subTracks[i]
            foreach (Track t_subTrack in subTracks[i])
            {
                if (t_subTrack != null)
                {
                    Console.Write("\t");
					t_subTrack.printInfo();
                }
            }
        }
    }

    // retroactively removes track[t_ID] when it clears out the data of t_TG and then doesn't add it back into other groups
	public bool removeTrack(Track t_Track){
		TrackGroup t_TG = searchForContainingTrackGroup (t_Track);
		
		if (t_TG != null){
			Track[] t_Neighbors = t_Track.getNeighbors();
			
			foreach(Track T in t_Neighbors){
				T.removeNeighbor(t_Track);
				t_Track.removeNeighbor(T);
			}
			
			HashSet<Track>[] subTracks = TrackIterator.findBrokenPaths(t_TG, t_Neighbors);
			
			if (subTracks == null){
				return false;
			}
			
			printSubTracks(subTracks);
			
			if (subTracks.Length > 1){
				t_TG.clearList();
				
				for (int i = 0; i < subTracks.Length; i++){
					bool foundEmpty = false;
					
					foreach (TrackGroup t__TG in TG_List){
						if (t__TG.getNumMembers() == 0){// empty found
							foreach (Track t__Track in subTracks[i]){
								if (t__Track != t_Track){
									t__TG.addTrack(t__Track);
								}
							}
							
							foundEmpty = true;
							break;
						}
					}
					
					if (!foundEmpty){
						TG_List.Add (new TrackGroup(subTracks[i]));
					}
				}
			}
			return true;
		}
		else{
			return false;
		}
	}
	/* This entire section commented out but left in for clarity and because I don't want to retype all of the comments again.
	 * Functionally the same as the method immediately above
	 * 
    public bool removeTrack(int t_ID) // find and remove track by track ID
    {
		
        // first get the trackgroup that t_ID is a part of
        TrackGroup t_TG = searchForContainingTrackGroup(t_ID); // a valid TrackGroup if found, null if not found
        
        // check to see if t_TG is valid
        if (t_TG != null)
        {
			#region hideme
            // now get the Track and its neighbors
            Track t_Track = t_TG.getTrackByID(t_ID);
            Track[] t_Neighbors = new Track[t_Track.getNumNeighbors()];
            int[] t_TrackNeighborIDs = t_Track.getNeighbors();
                // populate t_Neighbors
            for (int i = 0; i < t_TrackNeighborIDs.Length; i++)
            {
                t_Neighbors[i] = t_TG.getTrackByID(t_TrackNeighborIDs[i]);
            }

                // remove references between t_Track and t_Neighbors
            foreach (Track T in t_Neighbors) // not removing neighbors properly
            {
                T.removeNeighbor(t_Track.ID);
                t_Track.removeNeighbor(T.ID);
                //T.removeNeighbor(T.getID());
            }
            
                // get a list of subtracks
            List<Track>[] subTracks = TrackIterator.findBrokenPaths(t_TG, t_Neighbors);

            // if subTracks for some reason == null then end this method
            if (subTracks == null)
            {
                return false;
            }// otherwise just continue as normal

            // print subTracks list
            printSubTracks(subTracks);
			#endregion
            // since we have our list of subtracks, check if the number of subtracks is > 1; if so then empty t_TG and discard it
            // so we can populate it anew as well as any other empty TrackGroups first
            if (subTracks.Length > 1)
            {
                t_TG.clearList();

                // now that t_TG is clear we can start on populating everything
                for (int i = 0; i < subTracks.Length; i++)
                {// iterate through each subTracks list; try to find an empty list to populate or, failing that, create a new one to populate
					#region hideme
                    bool foundEmpty = false;
                    // first, look through TG_List to find an empty list
                    foreach (TrackGroup t__TG in TG_List)
                    {
                        if (t__TG.getNumMembers() == 0) // empty list found!
                        {
                            // now iterate through subTracks[i] Track members and push them into t__TG
                            foreach (Track t__Track in subTracks[i])
                            {
                                // make sure that they are not adding in Track[t_ID] while going through this
                                if (t__Track.ID != t_ID)
                                {
                                    t__TG.addTrack(t__Track);
                                }
                            }
                            // we have found an empty one so no need to keep searching
                            foundEmpty = true;
                            break;
                        }
                    }
					#endregion
                    // if we are unable to find an empty TrackGroup we need to make one
                    if (!foundEmpty)
                    {
                        TG_List.Add(new TrackGroup(subTracks[i])); // automatically populates TrackGroup
                        // need to remove Track[t_ID] from this new TrackGroup
                        //Track vt_Track = TG_List.Last().getTrackByID(t_ID);
                        //TG_List.Last().removeTrack(vt_Track);
                    }
                }
            }// otherwise do nothing else and no tracks have to be moved around so go ahead and exit by returning True

            return true;
        }
        else
        {
            Console.WriteLine("Track[{0}] Not found!", t_ID);
            return false; // in this case the t_ID is not found
        }
    }
	*/
	
	public bool addGroup(TrackGroup dummy){ // TODO finish working on this part!
		// first just add the dummy trackgroup into the manager
		TG_List.Add (dummy);
		
		float xHigh=0, yHigh=0, zHigh=0, xLow=0, yLow=0, zLow=0;
		Vector3 boxHigh, boxLow;
		
		// get TrackGroup dummy's bounding box
		foreach (Track track in dummy.getAllTracks()){
			if (track == dummy.getAllTracks().First()){ // if the first track in the track set then set high and low to the first track's coord values
				xHigh = xLow = track.coord.x;
				yHigh = yLow = track.coord.y;
				zHigh = zLow =track.coord.z;
			}else{
				// check x high and low
				if (track.coord.x > xHigh){
					xHigh = track.coord.x;
				}else if(track.coord.x < xLow){
					xLow = track.coord.x;
				}
				// check y high and low
				if (track.coord.y > yHigh){
					yHigh = track.coord.y;
				}else if(track.coord.y < yLow){
					yLow = track.coord.y;
				}
				// check z high and low
				if (track.coord.z > zHigh){
					zHigh = track.coord.z;
				}else if(track.coord.z < zLow){
					zLow = track.coord.z;
				}
			}
		}
		
		// currently these are very constrained and will actually have a box high and low slightly inside of the actual TrackGroup bounds
		// TODO fix to work with rotation as well, probably by checking each side (front/back) of the track on bounds to get the actual bounds
		boxHigh = new Vector3(xHigh,yHigh,zHigh);
		boxLow = new Vector3(xLow, yLow, zLow);
		
		// now that we have our bounding box for the dummy group we need to check to see if there are any nearby TrackPieces from other group(s)
		HashSet<Track> possibleNeighbors = new HashSet<Track>(); // any Tracks that are within the bounding box are placed here
		
		foreach (TrackGroup TG in TG_List){ // iterate through all TrackGroups
			if (TG != TG_List.Last()){ // except this new group that we just added
				foreach (Track T in TG.getAllTracks()){
					if (isBetween(boxHigh, boxLow, T.coord)){
						possibleNeighbors.Add (T);
					}
				}
			}
		}
		
		// now that we have all of our possible neighbors we can check each one for actual proximity to our track pieces,
		// get a list of actual connections, and connect the associated TrackGroups
		
		// if I rewrite the bool addTrack(...) routine then this may be the end of this section, just checking and adding the connections between tracks
		
		
		return false; // if for some reason everything fails
	}
	
	public bool isBetween(Vector3 high, Vector3 low, Vector3 input){
		bool lowCheck = false, highCheck = false;
		
		// check high first
		if (high[0] > input[0] && high[1] > input[1] && high[2] > input[2]){
			highCheck = true;
		}
		// check low
		if (low[0] < input[0] && low[1] < input[1] && low[2] < input[2]){
			lowCheck = true;
		}
		
		// returns TRUE if the coordinate vector "input" is completely within the bounding box, otherwise false
		
		return lowCheck && highCheck;
	}
	
    public bool addTrack(Track dummy)
    {
        // Create a dummy Track object to place into a TrackGroup
        // Track dummy = new Track(xyz, abc), cullTrack;

        // Iterator that is incremented whenever the dummy track fulfulls the requirements to be added to a TrackGroup
        int numValidConnections = 0;
        float maxDistance = 1.2f; // used as first cull value

        List<Track> TG_F_CullIDs = new List<Track>();
        

        // See if this dummy track can connect to any locations within any TrackGroups in TG_List
        foreach (TrackGroup TG in TG_List){
            // disregard any TG that has zero members
            //Console.WriteLine("TG.getNumMembers = {0}", TG.getNumMembers());
            if (TG.getNumMembers() > 0) // only do something with TG if it has at least 1 member
            {
                // some crap to cull out any TGs that are far away ( distance > maxDistance from any/all members)
                
                Track cullTrack = (TG.getClosestMember(dummy, maxDistance)); // just add the IDs to the list to make things easier
                
                if (cullTrack != null)
                {
                    //Console.WriteLine("cullTrack = {0}", cullTrack.getID());
                    TG_F_CullIDs.Add(cullTrack);
                }
            }
        }
        // now that TG_F_CullIDs has been populated, if it has been populated that is, now check to see if dummy track can connect to any
        // of the Tracks in TG_F_CullIDs
        foreach (Track i in TG_F_CullIDs)
        {
            // for now let's just say that if the item is in TG_F_CullIDs then it is a valid connection
            numValidConnections++;
        }
        //Console.WriteLine("numValidConnections = {0}", numValidConnections);
        //Console.WriteLine("TG_F_CullIDs.Count = {0}", TG_F_CullIDs.Count());
        if (numValidConnections == 0)
        {
            // try to find a TrackGroup with 0 members, if none are found then create new
            foreach (TrackGroup TG in TG_List)
            {
                if (TG.getNumMembers() == 0)
                {
                    TG.addTrack(dummy);
                    return true; // easy way to scoot out of this section quickly
                }
            }
            // if no empty track group is found, create a new one!
            TG_List.Add(new TrackGroup(dummy));
            
            return true;
        }
        else // there are valid connections, now just need to figure out how to work them in together
        {
            // print out all connecting IDs
            /*
            foreach (Track T in TG_F_CullIDs)
            {
                Console.WriteLine("\tConnection: Track[{0}]", T.getID());
            }
            Console.WriteLine();
            */
            if (numValidConnections == 1)
            {
                foreach (TrackGroup TG in TG_List)
                {
                    // for each TrackGroup in the TrackGroup list search for the first valid connection in TG_F_CullIDs
                    if (TG.contains(TG_F_CullIDs.ElementAt(0)))
                    {
                        dummy.addNeighbor(TG_F_CullIDs.ElementAt(0));
                        TG_F_CullIDs.ElementAt(0).addNeighbor(dummy);

                        TG.addTrack(dummy);
                    }
                }
            }
            else
            {
                // now to figure out which TrackGroups are being connected
                List<TrackGroup> TrackGroupConnectionSet = new List<TrackGroup>();

                foreach (TrackGroup TG in TG_List)
                {
                    foreach (Track T in TG_F_CullIDs)
                    {
                        if (TG.contains(T))
                        {
                            TrackGroupConnectionSet.Add(TG);
                            break; // makes sure that a TrackGroup is only added to the ConnectionSet once
                        }
                    }
                }

                foreach (Track T in TG_F_CullIDs)
                {
                    dummy.addNeighbor(T);
                    T.addNeighbor(dummy);
                }

                foreach (TrackGroup TG in TrackGroupConnectionSet)
                {
                    if (TG == TrackGroupConnectionSet.ElementAt(0))
                    {
                        continue; // skip the first group because it will always be the lowest index TrackGroup
                    }
                    else
                    {
                        TrackGroupConnectionSet.ElementAt(0).merge(TG.getAllTracks());
                        TG.clearList();
                    }
                }
                TrackGroupConnectionSet.ElementAt(0).addTrack(dummy);
                
            }
            return false;
        }
    }

    public void printItems(bool groups, bool tracks)
    {
        Console.WriteLine("TRACKMANAGER (TMAN) Member List");
        Console.WriteLine("\tShow Groups: " + groups);
        Console.WriteLine("\tShow Tracks: " + tracks);

        foreach (TrackGroup TG in TG_List)
        {
            if (groups)
            {
                Console.Write("TrackGroup[{0}] has {1} Tracks\n", TG.ID, TG.getNumMembers());
            }
            if (tracks){
                TG.printMembers();
            }
        }
    }
}